package com.nonagon.melladesk.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nonagon.melladesk.annotation.Login;
import com.nonagon.melladesk.utils.JwtUtil;
import com.nonagon.melladesk.utils.R;
import com.nonagon.melladesk.utils.StatusCode;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author flitsneak
 * @ClassName spring security reactive组件webfilter改造为jwt过滤器
 * @date 2021/3/19 17:35
 */
@Component
@Slf4j
@AllArgsConstructor
public class JwtWebFilter implements WebFilter {

    private final JwtUtil jwtSigner;

    private final ReactiveRedisTemplate<String, String> reactorTemplate;


    //异常信息统一捕获返回
    protected Mono<Void> writeErrorMessage(ServerHttpResponse response, R r) {
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        ObjectMapper mapper = new ObjectMapper();
        String body;
        try {
            body = mapper.writeValueAsString(r);
        } catch (JsonProcessingException e) {
            return Mono.error(e);
        }
        //按格式输出信息
        DataBuffer dataBuffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8));
        return response.writeWith(Mono.just(dataBuffer));
    }

    //jwt过滤
    @SneakyThrows
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {

        //下面有两种方案，一种是根据请求路径，一种是反射判断是否需要登录注解
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        //获取请求路径
        String path = request.getPath().value();
        //根据请求路径放行
        if (path.contains("/auth/login") || path.contains("/auth/signout")  || path.contains("swagger") || path.contains("v3"))
            return chain.filter(exchange);

        //根据注解放行
        //反射获取注解
        //1.设计一个正则表达式
        //2.使用正则表达式截取字符串
        String regex = "(?<=melladesk/)(.*)(?=/)";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(path);
        matcher.find();
        String s = matcher.group();
        String className = "com.nonagon.melladesk.handler." + s + "Handler";
        //加载类
        Class<?> clazz = Class.forName(className);
        //获取请求的方法
        Method[] methods = clazz.getMethods();
        ArrayList<Method> updateVitalsTemperatureByVitalId = Arrays.stream(methods)
                .filter(x -> x.toString().contains("updateVitalsTemperatureByVitalId"))
                .collect(Collectors.toCollection(ArrayList::new));
        //判断是否标注了注解
        boolean b = updateVitalsTemperatureByVitalId.get(0).isAnnotationPresent(Login.class);
        if (b == false) {
            return chain.filter(exchange);
        }

        //获取请求头携带的token
        String auth = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
        //如果token为空抛错
        if (auth == null) {
            return this.writeErrorMessage(response, new R(false, StatusCode.TOKENBLANKERROR.getCode(), StatusCode.TOKENBLANKERROR.getMsg()));
            //如果token没有以Bearer开头则报错
        } else if (!auth.startsWith(jwtSigner.getTokenPrefix())) {
            return this.writeErrorMessage(response, new R(false, StatusCode.TOKENERROR.getCode(), StatusCode.TOKENERROR.getMsg()));
        }

        String token = auth.substring(jwtSigner.getTokenPrefix().length());

        //判断redis中是否有该jsonwebtoken
        return reactorTemplate.opsForSet().isMember("token_set", token)
                .flatMap(isMember -> {//这里有坑，注意序列化方式要换成stringserializer
                    if (isMember) {
                        try {
                            exchange.getAttributes().put("token", token);
                            return chain.filter(exchange);
                        } catch (Exception e) {
                            return this.writeErrorMessage(response, new R(false, StatusCode.ERROR.getCode(), StatusCode.ERROR.getMsg()));
                        }
                    } else {
                        return this.writeErrorMessage(response, new R(false, StatusCode.TOKENFORGE.getCode(), StatusCode.TOKENFORGE.getMsg()));
                    }
                });
    }
}
